import utils from './utils';

const DEFAULT_EVENTS = ['scroll', 'wheel', 'touchmove'];
function remove(arr, el) {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i].el === el) {
      arr.splice(i, 1);
      break;
    }
  }
}
function ViewBox({ el, $parent, show, data }) {
  this.state = '';
  this.el = el;
  this.$parent = $parent;
  this.rect = null;
  this.show = show;
  this.data = data;
}
ViewBox.prototype = {
  getBoxBounding() {
    if (typeof this.$parent.getBoundingClientRect === 'function') {
      const { left, top, bottom, right } = this.$parent.getBoundingClientRect();
      return {
        left: Math.max(0, left),
        top: Math.max(0, top),
        bottom: Math.min(window.innerHeight, bottom),
        right: Math.min(window.innerWidth, right)
      };
    }
    // window box
    return { left: 0, top: 0, bottom: window.innerHeight, right: window.innerWidth };
  },
  checkInView() {
    const rect = this.el.getBoundingClientRect();
    const { top, bottom, left, right } = rect;
    if (top === 0 && bottom === 0 && left === 0 && right === 0) {
      return false;
    }
    const box = this.getBoxBounding();
    const isVisible = this.el.style.display !== 'none';
    const isInView =
      rect.top + 2 >= box.top &&
      rect.bottom - 2 <= box.bottom &&
      rect.left + 2 >= box.left &&
      rect.right - 2 <= box.right;
    return isVisible && isInView;
  },
  update({ show, data }) {
    this.show = show;
    this.data = data;
    this.state = '';
  },
  exposure() {
    this.state = 'exposure';
    const { show, data } = this;
    if (typeof show === 'function') {
      show(data);
    }
  }
};

function ContainerBox({ listenEvents }) {
  this.ListenerQueue = [];
  this.TargetQueue = [];
  this.TargetIndex = 0;
  this.ListenEvents = listenEvents || DEFAULT_EVENTS;
  this.checkViewHandler = utils.throttle(this.$_checkViewHandler.bind(this), 500);
}
ContainerBox.prototype = {
  add(el, binding) {
    const hasPush = this.ListenerQueue.find(item => item.el === el);
    if (hasPush) {
      this.update(el, binding);
      return;
    }
    // console.log('add -->');
    // parentNode 挂载后获取父节点
    let $parent;
    if (binding.modifiers.window) {
      $parent = window;
    } else {
      $parent = utils.scrollParent(el);
    }
    const { show, data } = binding.value;
    const viewBox = new ViewBox({
      $parent,
      el,
      show,
      data
    });
    this.ListenerQueue.push(viewBox);

    // 添加滚动事件触发曝光
    this.$_addListenerTarget(window);
    this.$_addListenerTarget($parent);
    // this.checkViewHandler();
    utils.nextTick(() => this.checkViewHandler());
  },
  update(el, binding) {
    // console.log('update -->');
    const exist = this.ListenerQueue.find(item => item.el === el);
    if (exist) {
      exist.update(binding.value);
      // this.checkViewHandler();
      utils.nextTick(() => this.checkViewHandler());
    }
  },
  remove(el) {
    // console.log('remove -->');
    if (!el) return;
    let index = -1;
    const existItem = this.ListenerQueue.find((item, i) => {
      if (item.el === el) {
        index = i;
        return true;
      }
      return false;
    });
    if (existItem) {
      this.$_removeListenerTarget(existItem.$parent);
      this.$_removeListenerTarget(window);
      this.ListenerQueue.splice(index, 1);
    }
  },
  $_checkViewHandler() {
    let catIn = false;
    this.ListenerQueue.forEach(listener => {
      if (listener.state === 'exposure') return;
      catIn = listener.checkInView();
      if (!catIn) return;
      // console.log('exposured element --->', listener.el);
      listener.exposure();
      setTimeout(() => {
        // 异步删除数组，解决连续曝光不生效问题
        remove(this.ListenerQueue, listener.el);
      }, 0);
    });
    // console.log('check in view...', this.ListenerQueue.slice(0));
  },
  $_initListen(el, start) {
    this.ListenEvents.forEach(evt => utils._[start ? 'on' : 'off'](el, evt, this.checkViewHandler));
  },
  $_addListenerTarget(el) {
    if (!el) return;
    let target = this.TargetQueue.find(item => item.el === el);
    if (!target) {
      target = {
        el,
        id: ++this.TargetIndex,
        childrenCount: 1,
        listened: true
      };
      this.$_initListen(target.el, true);
      this.TargetQueue.push(target);
    } else {
      target.childrenCount++;
    }
  },
  $_removeListenerTarget(el) {
    this.TargetQueue.forEach((target, index) => {
      if (target.el === el) {
        target.childrenCount--;
        if (!target.childrenCount) {
          this.$_initListen(target.el, false);
          this.TargetQueue.splice(index, 1);
          target = null;
        }
      }
    });
  }
};
export default {
  install(Vue) {
    const lazy = new ContainerBox({});
    Vue.directive('exposure', {
      inserted: lazy.add.bind(lazy),
      update: lazy.update.bind(lazy),
      componentUpdated: lazy.checkViewHandler.bind(lazy),
      unbind: lazy.remove.bind(lazy)
    });
  }
};